package ss.pku.Scope;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import ss.pku.util.GlobalContainer;

/**
 * @author liangwei
 * 
 *         Every Source code file represents to this model.
 */
public class SourceFileScope extends AbstractScope {

	private String filePath = "";
	private String fileName = "";
	private String packageName = "";
	private String src = "";
	private CompilationUnit astTree;
	private PackageDeclaration packageDeclaration;
	private List<ImportDeclaration> importList;
	private List<ClassScope> classes;
	

	/**
	 * constructor
	 * 
	 * @param fileAbsolutePath
	 */
	public SourceFileScope(String fileAbsolutePath) {
		File file = new File(fileAbsolutePath);
		if (!file.exists()) {
			throw new InvalidParameterException("This file is not existed!");
		}
		if (!file.getName().endsWith("java")) {
			throw new InvalidParameterException(
					"This file is not java source file");
		}
		this.filePath = fileAbsolutePath;
		this.fileName = (file.getName().replace(".java", ""));

		/**
		 * set src -- the entire source file
		 */
		try {
			StringBuffer buffer = new StringBuffer();
			BufferedReader reader = new BufferedReader(new FileReader(file));
			String line = null;
			while (null != (line = reader.readLine())) {
				buffer.append(line);
				buffer.append("\n");
			}
			reader.close();
			this.src = buffer.toString();
		} catch (FileNotFoundException e) {
			throw new InvalidParameterException("This file is not existed!");
		} catch (IOException e) {
			throw new RuntimeException("IO problem");
		}

		/**
		 * set astTree
		 */
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		@SuppressWarnings("rawtypes")
		Map options = JavaCore.getOptions();
		JavaCore.setComplianceOptions(JavaCore.VERSION_1_5, options);
		parser.setCompilerOptions(options);
		parser.setResolveBindings(true);
		parser.setKind(ASTParser.K_COMPILATION_UNIT);
		parser.setSource(this.src.toCharArray());
		this.astTree = (CompilationUnit) parser.createAST(null);

		/**
		 * Set the import list
		 */
		this.importList = this.astTree.imports();

		/**
		 * Set the package Declaration and package name
		 */
		this.packageDeclaration = this.astTree.getPackage();
		if (this.packageDeclaration != null) {
			this.packageName = this.packageDeclaration.getName().toString();
		}

		/**
		 * Set the classes
		 */
		classes = new ArrayList<ClassScope>();
		List<TypeDeclaration> types = this.astTree.types();
		for (TypeDeclaration type : types) {
			ClassScope classScope = new ClassScope(type, astTree);
			classes.add(classScope);
		}
	}

	/**
	 * constructor
	 * 
	 * @param sourcePath
	 * @param cu
	 */
	public SourceFileScope(String sourcePath, CompilationUnit cu) {
		GlobalContainer.times=0;
		File file = new File(sourcePath);
		if (!file.exists()) {
			throw new InvalidParameterException("This file is not existed!");
		}
		if (!file.getName().endsWith("java")) {
			throw new InvalidParameterException(
					"This file is not java source file");
		}
		byte[] carry = new byte[1024];
		try {
			StringBuffer buffer = new StringBuffer();
			BufferedInputStream stream = new BufferedInputStream(
					new FileInputStream(file));
			int length = 0;
			while (-1 != (length = stream.read(carry))) {
				String str = new String(carry, 0, length);
				buffer.append(str);
			}
			stream.close();
			this.src = buffer.toString();

		} catch (FileNotFoundException e) {
			throw new InvalidParameterException("This file is not existed!");
		} catch (IOException e) {
			throw new RuntimeException("IO problem");
		}
		this.filePath = sourcePath;
		this.fileName = file.getName();
		this.fileName = (file.getName().replace(".java", ""));
		this.astTree = cu;
		if (this.astTree.getPackage() != null)
			this.packageName = this.astTree.getPackage().getName().toString();
		else
			this.packageName = "";

		/**
		 * Set the import list
		 */
		this.importList = this.astTree.imports();

		/**
		 * Set the package Declaration
		 */
		this.packageDeclaration = this.astTree.getPackage();

		/**
		 * Set the classes
		 */
		classes = new ArrayList<ClassScope>();
		List<TypeDeclaration> types = this.astTree.types();
		for (TypeDeclaration type : types) {
			ClassScope classScope = new ClassScope(type, cu);
			classes.add(classScope);
		}
	}

	public String getFileAbsolutePath() {
		return filePath;
	}

	public String getFileName() {
		return fileName;
	}

	public String getPackageName() {
		return packageName;
	}

	public String getSrc() {
		return src;
	}

	public ASTNode getAstTree() {
		return astTree;
	}

	public int getLineNumber(ASTNode node) {
		return astTree.getLineNumber(node.getStartPosition());
	}

	public void accept(ASTVisitor visitor) {
		this.astTree.accept(visitor);
	}

	public PackageDeclaration getPackageDeclaration() {
		return this.packageDeclaration;
	}

	public List<ImportDeclaration> getImportList() {
		return this.importList;
	}

	public List<ClassScope> getClasses() {
		return this.classes;
	}

	/**
	 * get location
	 */
	public int getStartLocation() {
		return this.astTree.getStartPosition();
	}

	public int getEndLocation() {
		return this.astTree.getStartPosition() + this.astTree.getLength();
	}

	public int getLength() {
		return this.astTree.getLength();
	}

}
